<cite>
**本文档引用的文件**
- [internal/models/task.go](file://internal/models/task.go)
- [internal/scheduler/scheduler.go](file://internal/scheduler/scheduler.go)
- [internal/state/state.go](file://internal/state/state.go)
- [internal/api/handler.go](file://internal/api/handler.go)
- [internal/executor/executor.go](file://internal/executor/executor.go)
- [cmd/execgo/main.go](file://cmd/execgo/main.go)
- [README.md](file://README.md)
</cite>
目录
简介
本文件系统性地定义 ExecGo 的任务模型,聚焦 Task 结构体及其相关类型,明确每个字段的语义、约束与生命周期,并给出字段验证规则、状态转换与重试/超时策略。目标是帮助开发者在不深入源码的情况下,也能准确理解并正确使用 ExecGo 的任务 DSL。
项目结构
ExecGo 的任务模型位于 internal/models/task.go,围绕该模型展开的调度、状态持久化、API 层与执行器分别位于 internal/scheduler、internal/state、internal/api 与 internal/executor。入口程序 cmd/execgo/main.go 负责初始化组件并启动服务。
graph TB
subgraph "应用入口"
MAIN["cmd/execgo/main.go"]
end
subgraph "API 层"
API["internal/api/handler.go"]
end
subgraph "调度器"
SCHED["internal/scheduler/scheduler.go"]
end
subgraph "状态管理"
STATE["internal/state/state.go"]
end
subgraph "模型定义"
MODELS["internal/models/task.go"]
end
subgraph "执行器"
EXEC["internal/executor/executor.go"]
end
MAIN --> API
API --> SCHED
SCHED --> STATE
SCHED --> EXEC
API --> MODELS
STATE --> MODELS
图表来源
- cmd/execgo/main.go:25-104
- internal/api/handler.go:28-52
- internal/scheduler/scheduler.go:18-45
- internal/state/state.go:17-53
- internal/models/task.go:21-39
- internal/executor/executor.go:14-67
章节来源
- cmd/execgo/main.go:25-104
- README.md:149-177
核心组件
本节聚焦 Task 结构体与相关类型,逐项解释字段含义、约束与默认行为。
-
TaskStatus 枚举
- pending:初始状态,等待调度
- running:正在执行
- success:执行成功
- failed:执行失败
- skipped:因上游依赖失败而被跳过
-
Task 结构体字段
- id:字符串,唯一标识符,必填且不可重复
- type:字符串,任务类型,必填;需与已注册执行器类型一致
- params:JSON 字节片段,类型相关参数,可选
- depends_on:字符串数组,上游依赖任务 ID 列表,可选
- retry:整数,最大重试次数(包含首次),默认 0 即仅尝试一次
- timeout:整数(毫秒),执行超时时间,<=0 表示无超时
- status:TaskStatus,任务状态
- result:JSON 字节片段,执行结果,可选
- error:字符串,错误信息,可选
- created_at:时间戳,任务创建时间
- updated_at:时间戳,最后更新时间
-
TaskGraph
- tasks:Task 数组,表示一次提交的任务 DAG
章节来源
- internal/models/task.go:10-39
- internal/models/task.go:41-79
架构总览
下图展示任务从提交到执行、状态更新与持久化的整体流程。
sequenceDiagram
participant Client as "客户端"
participant API as "API 层"
participant Sched as "调度器"
participant State as "状态管理"
participant Exec as "执行器"
participant Disk as "持久化文件"
Client->>API : "POST /tasks 提交任务图"
API->>API : "校验请求体与任务图"
API->>Sched : "Submit(TaskGraph)"
Sched->>State : "Put(任务) + 初始化状态为 pending"
Sched->>Sched : "构建依赖计数与反向依赖图"
Sched->>Sched : "入就绪队列(无依赖任务)"
loop "调度主循环"
Sched->>Sched : "获取并发槽"
Sched->>Exec : "按类型获取执行器"
Sched->>State : "更新状态为 running"
Sched->>Exec : "Execute(ctx, task) + 超时控制"
alt "执行成功"
Sched->>State : "更新状态为 success + 写入 result"
Sched->>Sched : "级联触发下游依赖"
else "执行失败"
Sched->>State : "更新状态为 failed + 写入 error"
Sched->>Sched : "级联跳过下游依赖"
end
end
State->>Disk : "周期性持久化 state.json"
图表来源
- internal/api/handler.go:58-99
- internal/scheduler/scheduler.go:69-97
- internal/scheduler/scheduler.go:127-190
- internal/scheduler/scheduler.go:192-222
- internal/state/state.go:110-134
详细组件分析
Task 结构体字段详解与约束
- id
- 必填:提交时必须非空
- 唯一:同一任务图内不可重复
- 用途:状态管理、依赖解析、持久化键
- type
- 必填:提交时必须非空
- 约束:必须与已注册执行器类型一致
- 作用:决定执行器选择
- params
- 可选:未提供时视为空对象
- 类型:JSON 字节片段,具体结构由执行器定义
- depends_on
- 可选:未提供时为空数组
- 约束:引用的 ID 必须存在于任务图中,且不可自依赖
- retry
- 可选:默认 0,即仅尝试一次
- 语义:最大重试次数(包含首次)
- timeout
- 可选:单位毫秒,<=0 表示无超时
- 控制:每次尝试均受独立的超时 context 控制
- status
- 初始值:提交后为 pending
- 生命周期:pending → running → success 或 failed 或 skipped
- result
- 可选:仅在成功时写入
- error
- 可选:仅在失败时写入
- created_at / updated_at
- 时间戳:创建时与每次状态更新时写入
章节来源
- internal/models/task.go:21-39
- internal/scheduler/scheduler.go:76-79
- internal/state/state.go:94-108
TaskStatus 状态转换与生命周期
- 初始状态
- Submit 后,所有任务状态设为 pending,created_at 与 updated_at 初始化
- 运行中
- 从 pending 入队并获取并发槽后,状态更新为 running
- 成功
- 执行成功则状态转为 success,写入 result,指标增加
- 失败
- 所有重试耗尽后仍未成功,状态转为 failed,写入 error,指标增加
- 跳过
- 上游依赖失败或被级联跳过时,状态转为 skipped,写入提示信息,指标增加
- 恢复
- 重启后,运行中的任务会被重置为 pending,避免重复执行
stateDiagram-v2
[*] --> pending
pending --> running : "入队并获取并发槽"
running --> success : "执行成功"
running --> failed : "重试耗尽"
running --> skipped : "上游依赖失败"
success --> [*]
failed --> [*]
skipped --> [*]
图表来源
- internal/scheduler/scheduler.go:139-142
- internal/scheduler/scheduler.go:186-189
- internal/scheduler/scheduler.go:207-211
- internal/state/state.go:41-50
章节来源
- internal/scheduler/scheduler.go:139-142
- internal/scheduler/scheduler.go:186-189
- internal/scheduler/scheduler.go:207-211
- internal/state/state.go:41-50
字段验证规则与约束
- 任务图不能为空
- 每个任务必须满足:
- id 非空
- type 非空
- id 在任务图内唯一
- 依赖关系:
- 依赖的 ID 必须存在
- 不可自依赖
- 通过拓扑排序检测环(Kahn 算法)
- 执行器类型:
- 提交前校验所有任务的 type 是否有对应执行器注册
flowchart TD
Start(["开始验证"]) --> CheckEmpty["检查任务图是否为空"]
CheckEmpty --> |为空| ErrEmpty["返回错误: 任务图为空"]
CheckEmpty --> |非空| LoopTasks["遍历每个任务"]
LoopTasks --> CheckID["校验 id 非空"]
CheckID --> |失败| ErrID["返回错误: 缺少 id"]
CheckID --> |通过| CheckType["校验 type 非空"]
CheckType --> |失败| ErrType["返回错误: 缺少 type"]
CheckType --> |通过| CheckUnique["校验 id 唯一"]
CheckUnique --> |重复| ErrDup["返回错误: 重复 id"]
CheckUnique --> |通过| CheckDeps["校验依赖引用"]
CheckDeps --> |未知| ErrUnknown["返回错误: 未知依赖"]
CheckDeps --> |自依赖| ErrSelf["返回错误: 自依赖"]
CheckDeps --> |通过| Topo["拓扑排序检测环"]
Topo --> |有环| ErrCycle["返回错误: 依赖环"]
Topo --> |无环| Done(["验证通过"])
图表来源
- internal/models/task.go:41-79
- internal/models/task.go:81-121
- internal/api/handler.go:76-85
章节来源
- internal/models/task.go:41-79
- internal/models/task.go:81-121
- internal/api/handler.go:76-85
重试与超时策略
- 重试
- 最大尝试次数 = retry + 1(至少 1 次)
- 指数退避:100ms × 2^(attempt-2),上限 10 秒
- 超时
- 每次尝试使用独立的 context,超时为 task.timeout 毫秒
- 若 task.timeout <= 0,则使用取消 context
- 执行器选择
- 按 task.type 从注册表获取执行器,不存在则直接标记失败
flowchart TD
Enter(["进入执行"]) --> InitAttempts["计算最大尝试次数 = retry+1"]
InitAttempts --> AttemptLoop{"尝试次数 <= 最大尝试次数?"}
AttemptLoop --> |否| Fail["标记 failed + 写入 error"]
AttemptLoop --> |是| BuildCtx["构建带超时/取消的 context"]
BuildCtx --> Exec["调用执行器 Execute(ctx, task)"]
Exec --> Ok{"执行成功?"}
Ok --> |是| Success["标记 success + 写入 result"]
Ok --> |否| Backoff["指数退避等待"]
Backoff --> AttemptLoop
图表来源
- internal/scheduler/scheduler.go:144-147
- internal/scheduler/scheduler.go:152-180
- internal/scheduler/scheduler.go:131-137
章节来源
- internal/scheduler/scheduler.go:144-147
- internal/scheduler/scheduler.go:152-180
- internal/scheduler/scheduler.go:131-137
级联依赖处理
- 成功完成
- 下游依赖的剩余依赖计数减一,归零则入队
- 失败/跳过
- 标记下游为 skipped,并递归对下游的下游继续跳过
- 指标相应增加
flowchart TD
Complete(["任务完成"]) --> Status{"状态"}
Status --> |success| Dec["下游依赖计数-1"]
Dec --> Zero{"计数<=0?"}
Zero --> |是| Enqueue["入队执行"]
Zero --> |否| Wait["等待上游"]
Status --> |failed| Skip["标记 skipped + 递归跳过"]
Status --> |skipped| Skip
图表来源
- internal/scheduler/scheduler.go:192-222
- internal/scheduler/scheduler.go:224-230
章节来源
- internal/scheduler/scheduler.go:192-222
- internal/scheduler/scheduler.go:224-230
状态持久化与恢复
- 内存存储
- 以 map[string]*Task 保存,读写锁保护
- 磁盘持久化
- 定期将内存快照序列化为 JSON 文件,先写临时文件再原子重命名
- 恢复策略
- 启动时加载 state.json,若存在则恢复;运行中任务在恢复时重置为 pending
sequenceDiagram
participant State as "状态管理"
participant Disk as "state.json"
State->>State : "周期性快照"
State->>Disk : "写入临时文件(.tmp)"
State->>Disk : "原子重命名覆盖"
State->>State : "启动时加载 state.json"
State->>State : "运行中任务重置为 pending"
图表来源
- internal/state/state.go:110-134
- internal/state/state.go:136-158
- internal/state/state.go:41-50
章节来源
- internal/state/state.go:110-134
- internal/state/state.go:136-158
- internal/state/state.go:41-50
依赖关系分析
- API 层负责接收任务图、进行校验与类型检查,然后提交给调度器
- 调度器负责:
- 初始化任务状态与时间戳
- 构建依赖计数与反向依赖图
- 并发控制与执行
- 状态更新与级联处理
- 状态管理器负责:
- 任务存储与查询
- 原子状态更新
- 持久化与恢复
- 执行器通过注册表按类型动态选择
graph LR
API["API 层"] --> SCHED["调度器"]
SCHED --> STATE["状态管理"]
SCHED --> EXEC["执行器注册表"]
STATE --> MODELS["Task/TaskGraph"]
EXEC --> MODELS
图表来源
- internal/api/handler.go:58-99
- internal/scheduler/scheduler.go:69-97
- internal/state/state.go:55-108
- internal/executor/executor.go:31-67
章节来源
- internal/api/handler.go:58-99
- internal/scheduler/scheduler.go:69-97
- internal/state/state.go:55-108
- internal/executor/executor.go:31-67
性能考量
- 并发控制
- 使用固定大小的信号量限制最大并发,避免资源争用
- 就绪队列
- 使用带缓冲通道承载就绪任务,减少阻塞
- 指数退避
- 降低重试风暴风险,提升系统稳定性
- 持久化策略
- 定期快照与原子重命名,降低写放大与损坏风险
章节来源
- internal/scheduler/scheduler.go:18-45
- internal/scheduler/scheduler.go:152-180
- internal/state/state.go:160-179
故障排查指南
- 提交任务报错“缺少 id”
- 检查任务图中是否存在空 id,确保每个任务都有唯一 id
- 提交任务报错“缺少 type”或“未知任务类型”
- 确认 type 与已注册执行器类型一致;可通过 /metrics 查看已注册类型
- 提交任务报错“重复 id”
- 确保任务图内 id 唯一
- 提交任务报错“未知依赖”
- 确认 depends_on 引用的 id 存在于任务图中
- 提交任务报错“自依赖”
- 删除自引用依赖
- 提交任务报错“依赖环”
- 检查依赖链路,消除环形依赖
- 任务长时间处于 running
- 检查 timeout 设置与执行器实现;确认执行器是否正确处理 context 取消
- 任务失败但未重试
- 确认 retry 设置;注意指数退避与最大退避上限
章节来源
- internal/models/task.go:41-79
- internal/models/task.go:81-121
- internal/api/handler.go:76-85
- internal/scheduler/scheduler.go:152-180
结论
ExecGo 的任务模型以简洁的 Task DSL 为核心,结合 DAG 调度、并发控制、重试与超时、状态持久化与可观测性,形成完整的执行闭环。遵循本文档的字段语义、验证规则与生命周期管理,可确保任务图的正确提交与稳定执行。
附录
- 任务 DSL 示例与参数规范可参考项目 README 的 Task DSL 规范与内置执行器参数说明
- 配置项与运行方式可参考 README 的快速开始与配置章节
章节来源
- README.md:181-213
- README.md:216-226